﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace theguineasyndrome
{
    public class Portal
    {
        public const int SIZE = 164;

        public enum Color
        {
            BLUE,
            ORANGE
        }

        private PortalableElement element;
        private LevelElement.Side side;
        private Color color;
        private Portal pairPortal;

        private Texture2D texture;
        private Animation animation;
        private Rectangle rectangle;
        private Rectangle srcRect;
        private Vector2 origin;
        private float rotation;

        private bool valid;

        public float CenterX
        {
            get { return rectangle.X; }
            set { rectangle.X = (int)value; }
        }

        public float CenterY
        {
            get { return rectangle.Y; }
            set { rectangle.Y = (int)value; }
        }

        public Vector2 Center
        {
            get { return new Vector2(CenterX, CenterY); }
        }

        public float Rotation
        {
            get { return rotation; }
        }

        public float Top
        {
            get { return CenterY - SIZE / 2; } // right only if side == LEFT or RIGHT
        }

        public float Bottom
        {
            get { return CenterY + SIZE / 2; } // right only if side == LEFT or RIGHT
        }

        public float Left
        {
            get { return CenterX - SIZE / 2; } // right only if side == TOP or BOTTOM
        }

        public float Right
        {
            get { return CenterX + SIZE / 2; } // right only if side == TOP or BOTTOM
        }

        public Portal(Portal oldPortal, Texture2D texture, PortalDrop drop)
        {
            this.texture = texture;
            this.animation = new Animation(texture, new Vector2(60, 186), 40, true);

            this.element = (PortalableElement)drop.getKillingObstacle();
            this.color = drop.getPortalColor();

            srcRect = new Rectangle(0, 0, texture.Width, texture.Height);
            origin = new Vector2(texture.Width / 2, texture.Height / 2);

            valid = true;

            calcPosition(oldPortal, drop.Position, drop.Speed);

            if (valid)
            {
                element.addPortal(this);

                animation.setScale(0);
                animation.setPosition(Center);
                animation.setOrigin(new Vector2(27, 93));
                animation.setRotation(rotation);

                if (side == LevelElement.Side.BOTTOM)
                    animation.setColor(new Microsoft.Xna.Framework.Color(0.3f, 0.3f, 0.3f, 0.3f));
            }
        }

        public bool isValid()
        {
            return valid;
        }

        public void setPairPortal(Portal portal)
        {
            if (portal != null)
                animation.setFrameLine(0);

            else
                animation.setFrameLine(1);

            this.pairPortal = portal;
        }

        public Portal getPairPortal()
        {
            return pairPortal;
        }

        public bool isActive()
        {
            return pairPortal != null;
        }

        private void setLeftOrRight(Portal oldPortal, Vector2 position, float x, float rotation, LevelElement.Side side)
        {
            if (element.CollisionHeight < SIZE)
            {
                valid = false;
                return;
            }

            this.side = side;
            CenterX = x;
            CenterY = position.Y;
            this.rotation = rotation;

            List<Portal> sidePortals = element.getPortalsAt(side);

            foreach (Portal portal in sidePortals)
            {
                if (portal != oldPortal)
                {
                    float distY = CenterY - portal.CenterY;

                    if (distY > 0 && distY < SIZE)
                        CenterY = portal.CenterY + SIZE;

                    else if (distY < 0 && -distY < SIZE)
                        CenterY = portal.CenterY - SIZE;
                }
            }

            if (CenterY < element.CollisionTop + SIZE / 2)
                CenterY = (int)(element.CollisionTop + SIZE / 2);

            else if (CenterY > element.CollisionBottom - SIZE / 2)
                CenterY = (int)(element.CollisionBottom - SIZE / 2);

            foreach (Portal portal in sidePortals)
            {
                if (portal != oldPortal)
                {
                    float distY = CenterY - portal.CenterY;

                    if (distY == 0 || distY > 0 && distY < SIZE || distY < 0 && -distY < SIZE)
                        valid = false;
                }
            }
        }

        private void setTopOrBottom(Portal oldPortal, Vector2 position, float y, float rotation, LevelElement.Side side)
        {
            if (element.Width < SIZE)
            {
                valid = false;
                return;
            }

            this.side = side;
            CenterX = position.X;
            CenterY = y;
            this.rotation = rotation;

            List<Portal> sidePortals = element.getPortalsAt(side);

            foreach (Portal portal in sidePortals)
            {
                if (portal != oldPortal)
                {
                    float distX = CenterX - portal.CenterX;

                    if (distX > 0 && distX < SIZE)
                        CenterX = portal.CenterX + SIZE;

                    else if (distX < 0 && -distX < SIZE)
                        CenterX = portal.CenterX - SIZE;
                }
            }

            if (CenterX < element.Left + SIZE / 2)
                CenterX = (int)(element.Left + SIZE / 2);

            else if (CenterX > element.Right - SIZE / 2)
                CenterX = (int)(element.Right - SIZE / 2);

            foreach (Portal portal in sidePortals)
            {
                if (portal != oldPortal)
                {
                    float distX = CenterX - portal.CenterX;

                    if (distX == 0 || distX > 0 && distX < SIZE || distX < 0 && -distX < SIZE)
                        valid = false;
                }
            }
        }

        private void setLeft(Portal oldPortal, Vector2 position)
        {
            setLeftOrRight(oldPortal, position, element.Left, (float)Math.PI, LevelElement.Side.LEFT);
        }

        private void setRight(Portal oldPortal, Vector2 position)
        {
            setLeftOrRight(oldPortal, position, element.Right, 0, LevelElement.Side.RIGHT);
        }

        private void setTop(Portal oldPortal, Vector2 position)
        {
            setTopOrBottom(oldPortal, position, element.CollisionTop, (float)-Math.PI / 2, LevelElement.Side.TOP);
        }

        private void setBottom(Portal oldPortal, Vector2 position)
        {
            setTopOrBottom(oldPortal, position, element.CollisionBottom, (float)Math.PI / 2, LevelElement.Side.BOTTOM);
        }

        private void calcPosition(Portal oldPortal, Vector2 position, Vector2 speed)
        {
            rectangle = new Rectangle(0, 0, texture.Width, texture.Height);

            side = element.getSide(position, speed);

            if (side == LevelElement.Side.LEFT)
                setLeft(oldPortal, position);
            else if (side == LevelElement.Side.TOP)
                setTop(oldPortal, position);
            else if (side == LevelElement.Side.RIGHT)
                setRight(oldPortal, position);
            else
                setBottom(oldPortal, position);
        }

        public void removeFromElement()
        {
            element.removePortal(this);
        }

        public void Update(GameTime gameTime)
        {
            animation.Update(gameTime);

            float scale = animation.getScale();
            if (scale < 1)
            {
                scale += (float)(gameTime.ElapsedGameTime.TotalMilliseconds / 100);

                if (scale > 1)
                    scale = 1;

                animation.setScale(scale);
            }
        }

        public void Draw(SpriteBatch spriteBatch, Camera camera)
        {
            animation.Draw(spriteBatch, camera);
            //spriteBatch.Draw(texture, camera.getDstRect(rectangle), srcRect, Microsoft.Xna.Framework.Color.White, rotation, origin, SpriteEffects.None, 0);
        }

        public LevelElement.Side getSide()
        {
            return side;
        }

        public Color getColor()
        {
            return color;
        }

        public PortalableElement getElement()
        {
            return element;
        }

        public bool isInside(Character character)
        {
            if (character.getCurrentPortal() != this)
                return false;

            bool inside = false;

            switch (side)
            {
                case LevelElement.Side.TOP:
                inside = character.SquareBottom >= CenterY && character.Left >= Left && character.Right <= Right;
                break;

                case LevelElement.Side.BOTTOM:
                inside = character.SquareTop <= CenterY && character.Left >= Left && character.Right <= Right;
                break;

                case LevelElement.Side.LEFT:
                inside = character.Right >= CenterX && character.SquareTop >= Top && character.SquareBottom <= Bottom;
                break;

                case LevelElement.Side.RIGHT:
                inside = character.Left <= CenterX && character.SquareTop >= Top && character.SquareBottom <= Bottom;
                break;
            }

            return inside;
        }

        public void repulseFromBorders(Character character)
        {
            if (side == LevelElement.Side.TOP || side == LevelElement.Side.BOTTOM)
            {
                if (character.SquareLeft < Left)
                {
                    character.setPosition(new Vector2(Left + character.Size / 2, character.Y));
                    character.SpeedX = 0;
                }
                else if (character.SquareRight > Right)
                {
                    character.setPosition(new Vector2(Right - character.Size / 2, character.Y));
                    character.SpeedX = 0;
                }
            }
            else if (side == LevelElement.Side.LEFT || side == LevelElement.Side.RIGHT)
            {
                if (character.SquareTop < Top)
                {
                    character.setPosition(new Vector2(character.X, Top + character.Height));
                    character.SpeedY = 0;
                }
                else if (character.SquareBottom > Bottom)
                {
                    character.setPosition(new Vector2(character.X, Bottom));
                    character.SpeedY = 0;
                }
            }
        }

        public void setDestination(Character character)
        {
            Vector2 pairPortalCenter = pairPortal.Center;
            LevelElement.Side pairPortalSide = pairPortal.getSide();

            //float oldCenterY = character.CenterY;

            Vector2 relativePosition = Center - character.Center;
            Vector2 polarRelativePosition = Utils.PolarCoordinates.fromCartesian(relativePosition);
            polarRelativePosition.X *= 0.95f;
            polarRelativePosition.Y += (float)(Rotation - pairPortal.Rotation);
            relativePosition = Utils.PolarCoordinates.toCartesian(polarRelativePosition);

            if ((side == LevelElement.Side.TOP || side == LevelElement.Side.BOTTOM) && (pairPortalSide == LevelElement.Side.BOTTOM || pairPortalSide == LevelElement.Side.TOP))
                relativePosition.Y *= -1;

            else if ((side == LevelElement.Side.LEFT || side == LevelElement.Side.RIGHT) && (pairPortalSide == LevelElement.Side.LEFT || pairPortalSide == LevelElement.Side.RIGHT))
                relativePosition.X *= -1;

            character.setPosition(pairPortalCenter + relativePosition + new Vector2(0, character.Height / 2));
            character.setCurrentPortal(pairPortal);

            Vector2 speed = character.Speed;

            Vector2 polarSpeed = Utils.PolarCoordinates.fromCartesian(speed);

            polarSpeed.Y += (float)(pairPortal.Rotation - rotation + Math.PI);
            speed = Utils.PolarCoordinates.toCartesian(polarSpeed);

            const float minSpeedTop = 12;
            const float minSpeed = 3;

            if (side == LevelElement.Side.TOP && pairPortalSide == LevelElement.Side.TOP && speed.Y > -minSpeedTop)
                speed.Y = -minSpeedTop;

            else if (speed.LengthSquared() < minSpeed * minSpeed)
            {
                speed.Normalize();
                speed *= minSpeed;
            }

            character.Speed = speed;
        }

        public void setDropDestination(LiquidDrop drop)
        {
            Vector2 pairPortalCenter = pairPortal.Center;
            LevelElement.Side pairPortalSide = pairPortal.getSide();

            Vector2 relativePosition = Center - drop.Position;
            Vector2 polarRelativePosition = Utils.PolarCoordinates.fromCartesian(relativePosition);
            polarRelativePosition.X *= 0.95f;
            polarRelativePosition.Y += (float)(Rotation - pairPortal.Rotation);
            relativePosition = Utils.PolarCoordinates.toCartesian(polarRelativePosition);

            if ((side == LevelElement.Side.TOP || side == LevelElement.Side.BOTTOM) && (pairPortalSide == LevelElement.Side.BOTTOM || pairPortalSide == LevelElement.Side.TOP))
                relativePosition.Y *= -1;

            else if ((side == LevelElement.Side.LEFT || side == LevelElement.Side.RIGHT) && (pairPortalSide == LevelElement.Side.LEFT || pairPortalSide == LevelElement.Side.RIGHT))
                relativePosition.X *= -1;

            drop.Position = pairPortalCenter + relativePosition;

            Vector2 speed = drop.Speed;

            Vector2 polarSpeed = Utils.PolarCoordinates.fromCartesian(speed);

            polarSpeed.Y += (float)(pairPortal.Rotation - rotation + Math.PI);
            speed = Utils.PolarCoordinates.toCartesian(polarSpeed);

            const float minSpeedTop = 9;
            const float minSpeed = 2;

            if (side == LevelElement.Side.TOP && pairPortalSide == LevelElement.Side.TOP && speed.Y > -minSpeedTop)
                speed.Y = -minSpeedTop;

            else if (speed.LengthSquared() < minSpeed * minSpeed)
            {
                speed.Normalize();
                speed *= minSpeed;
            }

            drop.Speed = speed;

            drop.CurrentPortal = pairPortal;
        }
    }
}
